package main

import (
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	r "math/rand"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
	"errors"
	"path/filepath"
	"io"
)

func main() {

	fmt.Println("run")

	argNum := len(os.Args)

	if argNum == 2 {
		secondArg := os.Args[1]
		switch secondArg {
		case "build":
			buildProject()
		}

	}

	if argNum >= 3 {
		secondArg := os.Args[1]
		inputArg := os.Args[2]
		switch secondArg {
		case "create":
			createProject(inputArg)
		}

	}
}

func buildProject() {
	fmt.Println("build all")

	if runtime.GOOS == "windows" {
		buildProjectWithWindows()
	}else{
		buildProjectWithLinux()
	}

	//移动静态文件
	CopyPath("config"+dirDot(), "build"+dirDot()+"config")
	CopyPath("storage"+dirDot(), "build"+dirDot()+"storage")
	fmt.Println("build success")
}

// linux下编译打包
func buildProjectWithLinux(){
	pwd, _ := execShell("pwd")
	pwd = strings.Replace(pwd, " ", "", -1)
	pwd = strings.Replace(pwd, "\r\n", "", -1)
	pwdArr := explode("/", pwd)
	if len(pwdArr) == 0 {
		return
	}
	projectName := pwdArr[len(pwdArr)-1]
	execShell( fmt.Sprintf("go build -o build/%s", projectName))
}

// windows下编译打包
func buildProjectWithWindows() {
	pwd, _ := execShell("cd")
	pwd = strings.Replace(pwd, " ", "", -1)
	pwd = strings.Replace(pwd, "\r\n", "", -1)
	pwdArr := explode("\\", pwd)
	if len(pwdArr) == 0 {
		return
	}
	projectName := pwdArr[len(pwdArr)-1]

	execShell( fmt.Sprintf("go build -o build/%s.exe", projectName))
}

func createProject(projectName string) {
	fmt.Println("create project" + projectName)

	projectPath := getProjectPath(projectName)

	err := os.Mkdir(projectPath, os.ModePerm)
	if err != nil {
		fmt.Println(fmt.Sprintf("create project error:%v", err))
		return
	}
	fmt.Println("create directory [" + projectPath + "]")

	projectDir := []string{"config", "storage", "build","http" + dirDot() + "controller", "http" + dirDot() + "middleware"}
	for _, item := range projectDir {
		subDir := getProjectPath(projectName, item)
		fmt.Println("create directory [" + subDir + "]")

		err := os.MkdirAll(subDir, os.ModePerm)
		if err != nil {
			fmt.Println(fmt.Sprintf("create project dir error:%v", err))
		}
	}

	writeMainFile(projectName)
	writeStorage(projectName)
	writeBuildDir(projectName)
	writeConfigFile(projectName)
	writeRouteFile(projectName)
	writeIndexFile(projectName)
	writeMiddlewareFile(projectName)
	fmt.Println("create project success!")
}

func writeBuildDir(projectName string) {
	gitignoreTpl = strings.Replace(gitignoreTpl, "\n", "", 1)
	path := getProjectPath(projectName, "build", ".gitignore")
	fmt.Println(path)

	err := ioutil.WriteFile(path, []byte(gitignoreTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func writeStorage(projectName string) {
	gitignoreTpl = strings.Replace(gitignoreTpl, "\n", "", 1)
	path := getProjectPath(projectName, "storage", ".gitignore")

	err := ioutil.WriteFile(path, []byte(gitignoreTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func writeMainFile(projectName string) {
	maingoTpl = strings.Replace(maingoTpl, "#projectname#", projectName, -1)
	maingoTpl = strings.Replace(maingoTpl, "\n", "", 1)
	path := getProjectPath(projectName, "main.go")

	err := ioutil.WriteFile(path, []byte(maingoTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func writeConfigFile(projectName string) {
	configTpl = strings.Replace(configTpl, "#appkey#", getRandAppKey(), -1)
	configTpl = strings.Replace(configTpl, "\n", "", 1)
	configPath := getProjectPath(projectName, "config", "config.toml")

	err := ioutil.WriteFile(configPath, []byte(configTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + configPath + "]")
}

func writeRouteFile(projectName string) {
	routesTpl = strings.Replace(routesTpl, "#projectname#", projectName, -1)
	routesTpl = strings.Replace(routesTpl, "\n", "", 1)
	path := getProjectPath(projectName, "http", "routes.go")

	err := ioutil.WriteFile(path, []byte(routesTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func writeIndexFile(projectName string) {
	indexgoTpl = strings.Replace(indexgoTpl, "#indexHtml#", fmt.Sprintf("`%s`", indexHtml), 1)
	indexgoTpl = strings.Replace(indexgoTpl, "\n", "", 1)

	path := getProjectPath(projectName, "http", "controller", "index.go")

	err := ioutil.WriteFile(path, []byte(indexgoTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func writeMiddlewareFile(projectName string) {
	middlewareTpl = strings.Replace(middlewareTpl, "\n", "", 1)
	path := getProjectPath(projectName, "http", "middleware", "auth.go")

	err := ioutil.WriteFile(path, []byte(middlewareTpl), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("create file [" + path + "]")
}

func dirDot() string {
	if runtime.GOOS == "windows" {
		return "\\"
	} else {
		return "/"
	}
}



//拷贝文件
func CopyFile(src, dst string) bool {
	if len(src) == 0 || len(dst) == 0 {
		return false
	}
	srcFile, e := os.OpenFile(src, os.O_RDONLY, os.ModePerm)
	if e != nil {
		panic(e)
	}
	defer srcFile.Close()

	dst = strings.Replace(dst, "\\", "/", -1)
	dstPathArr := strings.Split(dst, "/")
	dstPathArr = dstPathArr[0 : len(dstPathArr)-1]
	dstPath := strings.Join(dstPathArr, "/")

	dstFileInfo := GetFileInfo(dstPath)
	if dstFileInfo == nil {
		if e := os.MkdirAll(dstPath, os.ModePerm); e != nil {
			panic(e)
		}
	}
	//这里要把O_TRUNC 加上，否则会出现新旧文件内容出现重叠现象
	dstFile, e := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	if e != nil {
		panic(e)
	}
	defer dstFile.Close()
	//fileInfo, e := srcFile.Stat()
	//fileInfo.Size() > 1024
	//byteBuffer := make([]byte, 10)
	if _, e := io.Copy(dstFile, srcFile); e != nil {
		panic(e)
	} else {
		return true
	}

}

//拷贝目录
func CopyPath(src, dst string) bool {
	srcFileInfo := GetFileInfo(src)
	if srcFileInfo == nil || !srcFileInfo.IsDir() {
		return false
	}
	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			panic(err)
		}
		relationPath := strings.Replace(path, src, dirDot(), -1)
		dstPath := strings.TrimRight(strings.TrimRight(dst, "/"), "\\") + relationPath
		if !info.IsDir() {
			if CopyFile(path, dstPath) {
				return nil
			} else {
				return errors.New(path + " copy fail")
			}
		} else {
			if _, err := os.Stat(dstPath); err != nil {
				if os.IsNotExist(err) {
					if err := os.MkdirAll(dstPath, os.ModePerm); err != nil {
						panic(err)
						return err
					} else {
						return nil
					}
				} else {
					panic(err)
					return err
				}
			} else {
				return nil
			}
		}
	})

	if err != nil {
		panic(err)
	}
	return true

}

//判断文件或目录是否存在
func GetFileInfo(src string) os.FileInfo {
	if fileInfo, e := os.Stat(src); e != nil {
		if os.IsNotExist(e) {
			return nil
		}
		return nil
	} else {
		return fileInfo
	}
}

func getProjectPath(subdirs ...string) string {
	basepath := getGoPath()

	for _, item := range subdirs {
		basepath = basepath + dirDot() + item

	}

	return basepath
}

func getGoPath() string {
	stdout, _ := execShell("go env GOPATH")
	stdout = strings.Replace(stdout, "\n", "", -1)
	return stdout + dirDot() + "src"
}

func execShell(shell string) (stdout string, stderr string) {
	var cmd *exec.Cmd
	if runtime.GOOS == "windows" {
		cmd = exec.Command("cmd", "/C", shell)
	} else {
		cmd = exec.Command("/bin/bash", "-c", shell)
	}

	var out bytes.Buffer
	var stderrBuf bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderrBuf
	err := cmd.Run()
	if err != nil {
		fmt.Println("shell exec have an error", "err", err)
	}

	return out.String(), stderrBuf.String()
}

func getRandAppKey() string {
	randByte := RandomCreateBytes(24)
	h := md5.New()
	h.Write(randByte)
	return hex.EncodeToString(h.Sum(nil))
}

var alphaNum = []byte(`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`)

// RandomCreateBytes generate random []byte by specify chars.
func RandomCreateBytes(n int, alphabets ...byte) []byte {
	if len(alphabets) == 0 {
		alphabets = alphaNum
	}
	var bytes = make([]byte, n)
	var randBy bool
	if num, err := rand.Read(bytes); num != n || err != nil {
		r.Seed(time.Now().UnixNano())
		randBy = true
	}
	for i, b := range bytes {
		if randBy {
			bytes[i] = alphabets[r.Intn(len(alphabets))]
		} else {
			bytes[i] = alphabets[b%byte(len(alphabets))]
		}
	}
	return bytes
}

// 将字符串按字符拆成数组
func explode(delimiter, datastr string) (arr []string) {
	ret := strings.Split(datastr, delimiter)

	for _, item := range ret {
		if item != "" {
			arr = append(arr, item)
		}
	}

	return arr
}

var maingoTpl = `
package main

import (
	"gitee.com/zhucheer/orange/app"
	"#projectname#/http"
)

func main() {

	router := &http.Route{}
	app.AppStart(router)

}
`

var middlewareTpl = `
package middleware

import (
	"errors"
	"gitee.com/zhucheer/orange/app"
)

type Auth struct {
}

func NewAuth() *Auth {
	return &Auth{}
}

// Func implements Middleware interface.
func (w Auth) Func() app.MiddlewareFunc {
	return func(next app.HandlerFunc) app.HandlerFunc {
		return func(c *app.Context) error {

			// 中间件处理逻辑
			if c.Request().Header.Get("auth") == "" {
				c.ResponseWrite([]byte("auth middleware break"))
				return errors.New("auth middleware break")
			}

			return next(c)
		}
	}
}
`

var indexgoTpl = `
package controller

import (
	"gitee.com/zhucheer/orange/app"
)

func Welcome(c *app.Context) error {

	return c.ToString(#indexHtml#)
}

func AuthCheck(c *app.Context) error {

	return c.ToJson(map[string]interface{}{
		"auth": "auth is ok",
	})
}
`

var routesTpl = `
package http

import (
	"gitee.com/zhucheer/orange/app"
	"#projectname#/http/controller"
	"#projectname#/http/middleware"
)

type Route struct {
}

func (s *Route) ServeMux() {
	commonGp := app.NewRouter("")
	{
		commonGp.GET("/", controller.Welcome)
		
		commonGp.GET("/api", func(ctx *app.Context) error {
			return ctx.ToJson(map[string]interface{}{"data": "orange framework"})
		})
	}

	authGp := app.NewRouter("/auth", middleware.NewAuth())
	{
		authGp.ALL("/info", controller.AuthCheck)
	}

}
`

var configTpl = `
[app]
    name = "orange"
    key = "#appkey#"
    httpAddr = "127.0.0.1"
    httpPort = 8088
    maxBody = 2096157
    csrfVerify = true
    maxWaitSecond=120
    [app.logger]
        level = "INFO"
        type = "text"
        path = ""
        syncInterval = 2
    [app.session]
        isOpen = true
        timeout = 1800
    [app.upload]
        storage = "./storage/allimg"
        maxSize = 2096157
        ext = ["jpg","jpeg","gif","png"]
`
var gitignoreTpl = `*
!.gitignore
`


var indexHtml = `<html><head><title>Orange Web Framework</title><style type="text/css">p,form,ol,ul,li,dl,dt,dd,h3{margin:0;padding:0;list-style:none}.logo{	width:397px;	margin:0 auto;	}.menu{ height:50px; width:200px; margin:0 auto; margin-bottom:50px;}.menu li{ font-size:25px; line-height:50px; width:80px; text-align:center; float:left;}.menu li a{color:#777; text-decoration:none;}.menu li > a:hover{color:#777; text-decoration:underline;}</style></head><body><div class='logo'><img src=''></div><ul class='menu'><li><a href="https://gitee.com/zhucheer/orange" target="_blank">源码</a></li><li><a href="https://www.kancloud.cn/chase688/orange_framework" target="_blank">文档</a></li></ul></body></html>`